# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Gitlab::BackgroundMigration::BackfillDismissalReasonInVulnerabilityReads, schema: 20230612232000, feature_category: :vulnerability_management do # rubocop:disable Layout/LineLength
  let!(:namespace) { table(:namespaces).create!(name: 'namespace', type: 'Group', path: 'namespace') }
  let!(:user) { table(:users).create!(email: 'author@example.com', username: 'author', projects_limit: 10) }
  let!(:state_transitions) { table(:vulnerability_state_transitions) }
  let!(:projects) { table(:projects) }
  let!(:vulnerabilities) { table(:vulnerabilities) }
  let!(:vulnerability_reads) { table(:vulnerability_reads) }
  let(:detected_state) { 1 }
  let(:dismissed_state) { 2 }
  let(:resolved_state) { 3 }

  let!(:project) do
    projects.create!(
      path: 'project',
      namespace_id: namespace.id,
      project_namespace_id: namespace.id
    )
  end

  let(:vulnerability_scanner) do
    table(:vulnerability_scanners).create!(
      project_id: project.id,
      external_id: "test-scanner",
      name: "Test Scanner"
    )
  end

  let!(:vulnerability) do
    table(:vulnerabilities).create!(
      project_id: project.id,
      author_id: user.id,
      title: 'test',
      severity: 7,
      confidence: 7,
      report_type: 0
    )
  end

  let!(:vulnerability_read) do
    table(:vulnerability_reads).create!(
      vulnerability_id: vulnerability.id,
      project_id: project.id,
      scanner_id: vulnerability_scanner.id,
      report_type: vulnerability.report_type,
      severity: 7,
      state: dismissed_state,
      uuid: SecureRandom.uuid,
      dismissal_reason: nil
    )
  end

  let!(:state_transition) do
    state_transitions.create!(
      vulnerability_id: vulnerability.id,
      from_state: detected_state,
      to_state: dismissed_state,
      dismissal_reason: 3 # used_in_tests
    )
  end

  let!(:different_vulnerability) do
    vulnerabilities.create!(
      project_id: project.id,
      author_id: user.id,
      title: 'should not be changed',
      severity: 5,
      confidence: 5,
      report_type: 1
    )
  end

  let!(:different_vulnerability_read) do
    vulnerability_reads.create!(
      vulnerability_id: different_vulnerability.id,
      project_id: project.id,
      scanner_id: vulnerability_scanner.id,
      report_type: different_vulnerability.report_type,
      severity: 7,
      state: resolved_state, # resolved
      uuid: SecureRandom.uuid,
      dismissal_reason: nil
    )
  end

  let!(:different_state_transition) do
    state_transitions.create!(
      vulnerability_id: different_vulnerability.id,
      from_state: detected_state,
      to_state: resolved_state
    )
  end

  let!(:vulnerability_with_multiple_dismissals) do
    vulnerabilities.create!(
      project_id: project.id,
      author_id: user.id,
      title: 'Vulnerability with multiple dismissals',
      severity: 7,
      confidence: 3,
      report_type: 2
    )
  end

  let!(:multiple_dismissal_vulnerability_read) do
    vulnerability_reads.create!(
      vulnerability_id: vulnerability_with_multiple_dismissals.id,
      project_id: project.id,
      scanner_id: vulnerability_scanner.id,
      report_type: vulnerability_with_multiple_dismissals.report_type,
      severity: 7,
      state: 2, # dismissed
      uuid: SecureRandom.uuid,
      dismissal_reason: nil
    )
  end

  let!(:detected_to_dismissed) do
    state_transitions.create!(
      vulnerability_id: vulnerability_with_multiple_dismissals.id,
      from_state: detected_state,
      to_state: dismissed_state,
      dismissal_reason: 3 # used_in_tests
    )
  end

  let!(:dismissed_to_detected) do
    state_transitions.create!(
      vulnerability_id: vulnerability_with_multiple_dismissals.id,
      from_state: dismissed_state,
      to_state: detected_state
    )
  end

  let!(:detected_to_dismissed_again) do
    state_transitions.create!(
      vulnerability_id: vulnerability_with_multiple_dismissals.id,
      from_state: detected_state,
      to_state: dismissed_state,
      dismissal_reason: 4 # not_applicable
    )
  end

  let!(:dimissed_vulnerability_without_state_transition) do
    vulnerabilities.create!(
      project_id: project.id,
      author_id: user.id,
      title: 'Dismissed vulnerability without a state transition',
      state: dismissed_state, # dismissed
      severity: 7,
      confidence: 3,
      report_type: 2,
      dismissed_by_id: user.id,
      dismissed_at: Time.current
    )
  end

  let!(:vulnerability_read_without_state_transition) do
    table(:vulnerability_reads).create!(
      vulnerability_id: dimissed_vulnerability_without_state_transition.id,
      project_id: project.id,
      scanner_id: vulnerability_scanner.id,
      report_type: dimissed_vulnerability_without_state_transition.report_type,
      severity: 7,
      state: dismissed_state,
      uuid: SecureRandom.uuid,
      dismissal_reason: nil
    )
  end

  subject do
    described_class.new(
      start_id: vulnerability_read.id,
      end_id: vulnerability_read_without_state_transition.id,
      batch_table: :vulnerability_reads,
      batch_column: :id,
      sub_batch_size: 1,
      pause_ms: 0,
      connection: ApplicationRecord.connection
    ).perform
  end

  it 'creates new Vulnerability::StateTransition entries for dismissed Vulnerabilities without one' do
    expect { subject }.to change { state_transitions.count }.by(1)

    new_state_transition = state_transitions.last

    expect(new_state_transition.from_state).to eq(detected_state)
    expect(new_state_transition.to_state).to eq(dismissed_state)
    expect(new_state_transition.author_id).to eq(dimissed_vulnerability_without_state_transition.dismissed_by_id)
  end

  it 'populates dismissal_reason for vulnerability_reads records that need it' do
    expect { subject }.to change { vulnerability_read.reload.dismissal_reason }.from(nil).to(3)
  end

  it 'does not populate dismissal_reason for vulnerability_reads records that do not need it' do
    expect { subject }.not_to change { different_vulnerability_read.reload.dismissal_reason }
  end

  it 'does not insert new rows' do
    expect { subject }.not_to change { vulnerability_reads.count }
  end

  it 'uses the latest dismissal state transition' do
    expect { subject }.to change { multiple_dismissal_vulnerability_read.reload.dismissal_reason }.from(nil).to(4)
  end
end
